/* jcifs smb client library in Java
 * Copyright (C) 2004  "Michael B. Allen" <jcifs at samba dot org>
 *                   "Eric Glass" <jcifs at samba dot org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.spnego;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;

/**
 * SPNEGO response token (NegTokenTarg) used in SPNEGO authentication exchanges
 */
public class NegTokenTarg extends SpnegoToken {

    /**
     * Result code indicating unspecified result
     */
    public static final int UNSPECIFIED_RESULT = -1;
    /**
     * Result code indicating authentication completed successfully
     */
    public static final int ACCEPT_COMPLETED = 0;
    /**
     * Result code indicating authentication is incomplete and additional tokens required
     */
    public static final int ACCEPT_INCOMPLETE = 1;
    /**
     * Result code indicating authentication was rejected
     */
    public static final int REJECTED = 2;
    /**
     * Result code indicating MIC token is requested
     */
    public static final int REQUEST_MIC = 3;

    private ASN1ObjectIdentifier mechanism;

    private int result = UNSPECIFIED_RESULT;

    /**
     * Default constructor for NegTokenTarg
     */
    public NegTokenTarg() {
    }

    /**
     * Constructs a NegTokenTarg with the specified parameters
     * @param result the negotiation result code
     * @param mechanism the selected authentication mechanism OID
     * @param mechanismToken the response token for the selected mechanism
     * @param mechanismListMIC the MIC over the mechanism list
     */
    public NegTokenTarg(final int result, final ASN1ObjectIdentifier mechanism, final byte[] mechanismToken,
            final byte[] mechanismListMIC) {
        setResult(result);
        setMechanism(mechanism);
        setMechanismToken(mechanismToken);
        setMechanismListMIC(mechanismListMIC);
    }

    /**
     * Constructs a NegTokenTarg by parsing the provided token bytes
     * @param token the SPNEGO token bytes to parse
     * @throws IOException if parsing fails
     */
    public NegTokenTarg(final byte[] token) throws IOException {
        parse(token);
    }

    /**
     * Gets the negotiation result code
     * @return the result code
     */
    public int getResult() {
        return this.result;
    }

    /**
     * Sets the negotiation result code
     * @param result the result code to set
     */
    public void setResult(final int result) {
        this.result = result;
    }

    /**
     * Gets the selected authentication mechanism OID
     * @return the mechanism OID
     */
    public ASN1ObjectIdentifier getMechanism() {
        return this.mechanism;
    }

    /**
     * Sets the selected authentication mechanism OID
     * @param mechanism the mechanism OID to set
     */
    public void setMechanism(final ASN1ObjectIdentifier mechanism) {
        this.mechanism = mechanism;
    }

    @Override
    public byte[] toByteArray() {
        try {
            final ByteArrayOutputStream collector = new ByteArrayOutputStream();
            final ASN1OutputStream der = ASN1OutputStream.create(collector, ASN1Encoding.DER);
            final ASN1EncodableVector fields = new ASN1EncodableVector();
            final int res = getResult();
            if (res != UNSPECIFIED_RESULT) {
                fields.add(new DERTaggedObject(true, 0, new ASN1Enumerated(res)));
            }
            final ASN1ObjectIdentifier mech = getMechanism();
            if (mech != null) {
                fields.add(new DERTaggedObject(true, 1, mech));
            }
            final byte[] mechanismToken = getMechanismToken();
            if (mechanismToken != null) {
                fields.add(new DERTaggedObject(true, 2, new DEROctetString(mechanismToken)));
            }
            final byte[] mechanismListMIC = getMechanismListMIC();
            if (mechanismListMIC != null) {
                fields.add(new DERTaggedObject(true, 3, new DEROctetString(mechanismListMIC)));
            }
            der.writeObject(new DERTaggedObject(true, 1, new DERSequence(fields)));
            return collector.toByteArray();
        } catch (final IOException ex) {
            throw new IllegalStateException(ex.getMessage());
        }
    }

    @Override
    protected void parse(final byte[] token) throws IOException {
        try (ASN1InputStream der = new ASN1InputStream(token)) {
            ASN1TaggedObject tagged = (ASN1TaggedObject) der.readObject();
            final ASN1Sequence sequence = ASN1Sequence.getInstance(tagged, true);
            final Enumeration<?> fields = sequence.getObjects();
            while (fields.hasMoreElements()) {
                tagged = (ASN1TaggedObject) fields.nextElement();
                switch (tagged.getTagNo()) {
                case 0:
                    final ASN1Enumerated enumerated = ASN1Enumerated.getInstance(tagged, true);
                    setResult(enumerated.getValue().intValue());
                    break;
                case 1:
                    setMechanism(ASN1ObjectIdentifier.getInstance(tagged, true));
                    break;
                case 2:
                    final ASN1OctetString mechanismToken = ASN1OctetString.getInstance(tagged, true);
                    setMechanismToken(mechanismToken.getOctets());
                    break;
                case 3:
                    final ASN1OctetString mechanismListMIC = ASN1OctetString.getInstance(tagged, true);
                    setMechanismListMIC(mechanismListMIC.getOctets());
                    break;
                default:
                    throw new IOException("Malformed token field.");
                }
            }
        }
    }

}
